package com.intervalstudios.wiimotegrabber;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.FileHandler;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.net.InetSocketAddress;

import javax.bluetooth.LocalDevice;
import javax.bluetooth.BluetoothStateException;

import wiiremotej.*;
import wiiremotej.event.*;
import de.sciss.net.OSCMessage;
import de.sciss.net.OSCClient;

public class WiiMoteGrabber implements WiiRemoteDiscoveryListener, Runnable{
    private static Logger log = Logger.getLogger("WiiMoteGrabber");

	private String deviceAddress;
    private String oscMessageBaseAddress;
    private OSCClient client;
    private List<WiiRemoteListener> registeredWiiMotes;
    private BlockingQueue<Map<String,Object[]>> messages;

    public static byte[][] sensitivity1 = new byte[][]{
    	{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, (byte)0x64, 0x00, (byte)0xfe},
    	{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, (byte)0x96, 0x00, (byte)0xb4},
    	{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, (byte)0xaa, 0x00, (byte)0x64},
    	{0x02, 0x00, 0x00, 0x71, 0x01, 0x00, (byte)0xc8, 0x00, (byte)0x36},
    	{0x07, 0x00, 0x00, 0x71, 0x01, 0x00, (byte)0x72, 0x00, (byte)0x20},
    	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0x90, 0x00, (byte)0x41}
    };
    public static byte[][] sensitivity2 = new byte[][]{
    		{(byte)0xfd, (byte)0x05},
    		{(byte)0xb3, (byte)0x04},
    		{(byte)0x63, (byte)0x03},
    		{(byte)0x35, (byte)0x03},
    		{(byte)0x1F, (byte)0x03},
        	{(byte)0x40, (byte)0x00}
    };
    public static int wiimoteSensitivity = 0;
    
    public static void main(String[] args) {
        Map<String,String> options = new HashMap<String,String>();
        //set some default options
        options.put("targetIP","192.168.1.106");
        options.put("targetPort","10001");
        options.put("logToFile","false");
        options.put("wiimoteSensitivity","2");
        for (String arg : args){
             String[] option = arg.split("=");
            if (option.length == 2 && options.containsKey(option[0])){
                options.put(option[0],option[1]);
            }else {
                log.info("Unknown or illformatted option: " + arg);
            }
        }
        
        if(options.get("logToFile").equals("true")){
        	configureLogger();   
        }
        int sens = Integer.parseInt(options.get("wiimoteSensitivity"));
        if(sens>0 && sens<sensitivity1.length) wiimoteSensitivity = sens;
        
        WiiMoteGrabber grabber = new WiiMoteGrabber(options.get("targetIP"), Integer.parseInt(options.get("targetPort")));
        new Thread(grabber).start();
        
    }

    
    private static void configureLogger(){

		String logDirPath = System.getProperty("user.dir") + "/logs/";
		File logDir = new File(logDirPath);
		logDir.mkdir();
     	
    	try{
        	log.setLevel(Level.FINE);
        	//addLoggerHandler(log, "info.log", Level.INFO);
    		FileHandler fh;
    		fh = new FileHandler(logDirPath + "fine.log", true);
    		SimpleFormatter formatter = new SimpleFormatter();
    		fh.setFormatter(formatter);
    		log.addHandler(fh);
    		fh.setLevel(Level.FINE);
    		
    		System.setOut( new PrintStream(new File(logDirPath + "/console.log")) );
    		System.setErr( new PrintStream(new File(logDirPath + "/console.log")) );
    	}catch(FileNotFoundException e){
    		log.severe(e.toString());
    	}catch(IOException e){
    		log.severe(e.toString());
    	}
      }

    public WiiMoteGrabber(String targetIP, int targetPort){
        setClient(targetIP, targetPort);
    }

    public void run() {
        //Start discovery threads
        log.info("Looking for wiimotes...");
        try{
        	WiiRemoteJ.findRemotes(this);
        }catch (Exception e){
        	log.severe("Exception in WiiRemoteJ.findRemotes:" + e.toString());
        	System.exit(0);
        }

        //Open a connection to the OSCServer
        try{
            log.info("Starting OSCClient...");
            getClient().start();
        }catch (IOException e){
            log.severe("Exception starging OSC client:" + e.toString());
        }

        //Relay OSCMessages as they arrive from WiiRemotes
        try {
            while(true) {
                Map<String,Object[]> message = getMessages().take();//blocks until a message arrives
                log.fine("message consumed. message: " + message.toString());
                String oscAdd = getOscMessageBaseAddress() + message.get("id")[0] + "/";
                for (Map.Entry<String,Object[]> submessage : message.entrySet()){
                    if (submessage.getKey().equals("disconnect")) { 
                    	deRegisterWiiMote(submessage.getValue()[0]); 
                    	submessage.setValue(new Object[]{1});//TODO need a better way to deregister, OSCCient can't serialize this, nor should it I guess...
                    }//we already used id in the address
                    if (submessage.getKey().equals("id")) { continue; }//we already used id in the address

                    try {//Construct a message such as /acceleration or /irdata
                        OSCMessage oscMsg = new OSCMessage(oscAdd + submessage.getKey(), submessage.getValue());                        
                        log.fine("sending OSC message: " + oscMsg.getName());
                        getClient().send(oscMsg);
                    } catch (IOException e){
                        log.severe("Exception sending OSC message:" + e.toString());
                    }
                }
            }
        }
        catch (InterruptedException e){
            log.warning(e.toString());
        }
    }

    public void wiiRemoteDiscovered(WiiRemoteDiscoveredEvent evt) {   	
        try {
        	WiiRemote remote = evt.getWiiRemote();
            enableWiiMote(remote);
        	String remoteAddress = remote.getBluetoothAddress();
        	WiiRemoteListenerImpl remoteListener = new WiiRemoteListenerImpl(getMessages(), remoteAddress, remote);
            getRegisteredWiiMotes().add(remoteListener);
            remote.addWiiRemoteListener(remoteListener);
            log.info("Wiimote Found. Total registered: " + getRegisteredWiiMotes().size());
        }catch (IOException e){
        	log.severe(e.toString());
        }   	
    }

    public void findFinished(int numberFound) {
        log.info("Found " + numberFound + " remotes!");
    }

    public void enableWiiMote(WiiRemote remote) throws IOException{
	
            remote.setAccelerometerEnabled(true);
			remote.setSpeakerEnabled(true);			
			remote.setIRSensorEnabled(true, WRIREvent.BASIC,sensitivity1[wiimoteSensitivity],sensitivity2[wiimoteSensitivity]);
//			byte[] sens1= {0x00 ,0x00, 0x00, 0x00, 0x00, 0x00,(byte) 0x90, 0x00, 0x41};
//			byte[] sens2= {0x40, 0x00};
//			remote.setIRSensorEnabled(true,WRIREvent.BASIC, sens1, sens2);
//			don't know what this does exactly, so commenting out until buttons are needed
//            remote.getButtonMaps().add(new ButtonMap(WRButtonEvent.HOME, ButtonMap.NUNCHUK, WRNunchukExtensionEvent.C, new int[]{java.awt.event.KeyEvent.VK_CONTROL},
//                    java.awt.event.InputEvent.BUTTON1_MASK, 0, -1));
	}

    public OSCClient getClient(){
        return client;
    }

    public void setClient(String targetIP, int targetPort){
        try {
            client = OSCClient.newUsing( OSCClient.UDP );
            client.setTarget(new InetSocketAddress(targetIP, targetPort));
        }
        catch( IOException e) {
            log.severe(e.toString());
        }
    }

    public synchronized BlockingQueue<Map<String,Object[]>> getMessages() {
        if (messages == null){
            messages = new LinkedBlockingQueue<Map<String,Object[]>>();
        }
        return messages;
    }
    
    public void deRegisterWiiMote(Object wiiMote){
    	deRegisterWiiMote((WiiRemoteListener)wiiMote);
    }
    
    public void deRegisterWiiMote(WiiRemoteListener wiiMote){
    	registeredWiiMotes.remove(wiiMote);
    }

    public synchronized List<WiiRemoteListener> getRegisteredWiiMotes(){
        if (registeredWiiMotes == null){
            registeredWiiMotes = new CopyOnWriteArrayList<WiiRemoteListener>();
        }
        return registeredWiiMotes;
    }

    public synchronized String getOscMessageBaseAddress(){
        if (oscMessageBaseAddress == null){
            oscMessageBaseAddress = "/wiimote/";
        }
        return oscMessageBaseAddress;
    }

    public synchronized String getDeviceAddress(){
        if (this.deviceAddress == null){
            try{
                this.deviceAddress = LocalDevice.getLocalDevice().getBluetoothAddress();
            }catch(BluetoothStateException e){
                log.severe("Error getting device address");
                log.severe(e.toString());
            }
        }
        return this.deviceAddress;
    }
}
